package mybatis.testDemo.datasource;

import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.Maps;
import lombok.Data;
import mybatis.testDemo.MybatisBean;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.StandardEnvironment;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static mybatis.testDemo.datasource.PgDataSource.pgDruidDataSource;

enum CurrentDataSource {
    PG(1, "pgDruidDataSource"), DRUID(2, "druidDataSource"), DEFAULT(3, "dataSource");
    private int index;
    private String val;

    CurrentDataSource(int i, String dataSource) {
        this.index = i;
        this.val = dataSource;
    }

    public int getIndex() {
        return index;
    }

    public String getVal() {
        return val;
    }

    public static String getValWithIndex(int index) {
        CurrentDataSource[] values = CurrentDataSource.values();

        CurrentDataSource currentDataSource = Arrays.stream(values).filter(val -> {
            if (val.getIndex() == index) {
                return true;
            }
            return false;
        }).findFirst().orElse(DEFAULT);
        return currentDataSource.getVal();

    }
}

@Configuration
public class CustomRouteDataSource {


    @Data
    @Configuration
    public static class RouteDataSourceConfigurationDemo {
        @Autowired
        Map<String, DataSource> map;
        volatile static CustomRoutingDataSource customRoutingDataSource;
        volatile static CustomRoutingDataSourceWithThreadLocal customRoutingDataSourceWithThreadLocal;

        /**
         * 锁定对象class 或者锁定对象，
         */
        @Primary
        @ConditionalOnProperty(name = "RouteDataSourceToTransformWithThreadLocal", havingValue = "threadLocal", matchIfMissing = true)
        @Bean
        public static CustomRoutingDataSourceWithThreadLocal customRoutingDataSourceWithThreadLocal(Map<String, DataSource> map) {
            if (ObjectUtil.isEmpty(customRoutingDataSourceWithThreadLocal)) {
                synchronized (RouteDataSourceConfigurationDemo.class) {
                    if (ObjectUtil.isEmpty(customRoutingDataSourceWithThreadLocal)) {
                        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
                        objectObjectHashMap.putAll(map);
                        customRoutingDataSourceWithThreadLocal = new CustomRoutingDataSourceWithThreadLocal(objectObjectHashMap);
                    }
                }
            }
            return customRoutingDataSourceWithThreadLocal;
        }

        @Primary
        @ConditionalOnProperty(name = "RouteDataSourceToTransformWithThreadLocal", havingValue = "atomic")
        @Bean
        public static CustomRoutingDataSource customRoutingDataSource(Map<String, DataSource> map) {
            if (ObjectUtil.isEmpty(customRoutingDataSource)) {
                synchronized (RouteDataSourceConfigurationDemo.class) {
                    if (ObjectUtil.isEmpty(customRoutingDataSource)) {
                        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
                        objectObjectHashMap.putAll(map);
                        customRoutingDataSource = new CustomRoutingDataSource(objectObjectHashMap);
                    }
                }
            }
            return customRoutingDataSource;
        }

    }


    public static void setupSqlSessionFactoryForRoute(AnnotationConfigApplicationContext applicationContext) throws SQLException {
//        BeanDefinition routeDataSourceConfigurationDemo = applicationContext.getBeanDefinition("routeDataSourceConfigurationDemo");
//        MutablePropertyValues propertyValues = routeDataSourceConfigurationDemo.getPropertyValues();
//        CustomRoutingDataSource customRoutingDataSource = (CustomRoutingDataSource)propertyValues.get("customRoutingDataSource");
        /**
         * fixme: 如何提前autowired ,目前初次只解析到了， routeDataSourceConfigurationDemo 子属性未成功注入，
         */
        DataSource pgDruidDataSource = pgDruidDataSource();
        Map<String, DataSource> map = Maps.newHashMap();
        map.put("pgDruidDataSource", pgDruidDataSource);
        map.put("druidDataSource", MybatisBean.MapperConfiguration.druidDataSource());

        StandardEnvironment environment = (StandardEnvironment) applicationContext.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(SqlSessionFactoryBean.class);
        // and new ConfigDataApplicationContextInitializer load propertiesSource,
        // CustomRoutingDataSource
        String threadLocal = environment.getProperty("RouteDataSourceToTransformWithThreadLocal");
        if (threadLocal == null || threadLocal.equals("threadLocal")) {
            CustomRoutingDataSourceWithThreadLocal customRoutingDataSourceWithThreadLocal = RouteDataSourceConfigurationDemo.customRoutingDataSourceWithThreadLocal(map);
            customRoutingDataSourceWithThreadLocal.afterPropertiesSet();
            definition.getPropertyValues().add("dataSource", customRoutingDataSourceWithThreadLocal);
        } else {
            CustomRoutingDataSource customRoutingDataSource = RouteDataSourceConfigurationDemo.customRoutingDataSource(map);
            customRoutingDataSource.afterPropertiesSet();
            definition.getPropertyValues().add("dataSource", customRoutingDataSource);
        }
        Interceptor sqlPrintInteceptor = MybatisBean.MapperConfiguration.SqlPrintInteceptor();
        // sql print
        Interceptor[] interceptors = {sqlPrintInteceptor};

        org.springframework.core.io.ClassPathResource classPathResource = new org.springframework.core.io.ClassPathResource("sqlmap/MapperInterface.xml");
        definition.getPropertyValues().add("mapperLocations", classPathResource);
        applicationContext.registerBeanDefinition("sqlSessionFactory", definition);
    }


}
